﻿Imports System.Text.RegularExpressions

''' <summary>
''' Provides enhanced writing to the console, and encapsulates CommandConsoleFramework markup formatting codes.
''' All console output of a CommandConsoleFramwork enabled application should be written through ConsoleOutput methods.
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class ConsoleOutput
    Public Shared Event ConsoleOutputWrite As EventHandler(Of ConsoleOutputWriteEventArgs)

    ''' <summary>
    ''' The character code for text color.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const ColorFormatCode As Char = "c"c
    ''' <summary>
    ''' The character code for back color.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const BackColorFormatCode As Char = "b"c
    ''' <summary>
    ''' The character code for cursor left.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const CursorLeftFormatCode As Char = "l"c
    ''' <summary>
    ''' The character code for cursor right.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const CursorRightFormatCode As Char = "r"c
    ''' <summary>
    ''' The character code for new line.
    ''' </summary>
    ''' <remarks></remarks>
    Public Const NewLineFormatCode As Char = "n"c
    ''' <summary>
    ''' The mask for a format code, where @ is the character code, and # is the paramter value (integer).
    ''' </summary>
    ''' <remarks></remarks>
    Public Const FormatCodeMask As String = "\[@#]"

    Public Shared Self As New ConsoleOutput
    ''' <summary>
    ''' Clears the console of text.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub ClearScreen()
        Console.Clear()
    End Sub

    ''' <summary>
    ''' Moves the cursor to the left side of the screen.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub CarrigeReturn()
        Console.CursorLeft = 0
    End Sub

    ''' <summary>
    ''' Moves the cursor to the next line at the same horizontal position.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub LineFeed()
        Console.CursorTop += 1
    End Sub

    ''' <summary>
    ''' Moves the cursor to the left side of the screen on the next line.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub NewLine()
        CarrigeReturn()
        LineFeed()
    End Sub

    ''' <summary>
    ''' Gets the text color format code string for the specified color.
    ''' </summary>
    ''' <param name="textColor"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetColorFormatCode(textColor As ConsoleColor) As String
        Return FormatCodeMask.Replace("@"c, "c"c).Replace("#", CInt(textColor).ToString)
    End Function

    ''' <summary>
    ''' Gets the back color format code string for the specified color.
    ''' </summary>
    ''' <param name="textColor"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetBackColorFormatCode(textColor As ConsoleColor) As String
        Return FormatCodeMask.Replace("@"c, "b"c).Replace("#", CInt(textColor).ToString)
    End Function

    ''' <summary>
    ''' Gets the format string to move the cursor left by [count] spaces.
    ''' </summary>
    ''' <param name="count"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetCursorLeftFormatCode(count As Integer) As String
        Return FormatCodeMask.Replace("@"c, "l"c).Replace("#", count.ToString)
    End Function

    ''' <summary>
    ''' Gets the format string to move the cursor right by [count] spaces.
    ''' </summary>
    ''' <param name="count"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetCursorRightFormatCode(count As Integer) As String
        Return FormatCodeMask.Replace("@"c, "r"c).Replace("#", count.ToString)
    End Function

    ''' <summary>
    ''' Gets the format code for the specified character code and parameter value. Returns and empty string if the character code is not valid.
    ''' </summary>
    ''' <param name="code"></param>
    ''' <param name="param"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetFormatCode(code As Char, param As Integer) As String
        Select Case code
            Case "c"c, "b"c, "l"c, "r"c, "n"c
                Return FormatCodeMask.Replace("@"c, code).Replace("#", param.ToString)
        End Select
        Return String.Empty
    End Function

    ''' <summary>
    ''' Gets the format string for the number of new lines specified by [count].
    ''' </summary>
    ''' <param name="count"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetNewLineFormatCode(count As Integer) As String
        Return FormatCodeMask.Replace("@"c, "n"c).Replace("#", count.ToString)
    End Function

    Protected Sub New()
    End Sub

    Protected Shared Sub DoWrite(text As String)
        Console.Write(text)
        RaiseEvent ConsoleOutputWrite(Self, New ConsoleOutputWriteEventArgs(text))
    End Sub

    Protected Shared Sub DoWriteLine(Optional text As String = Nothing)
        If text Is Nothing Then
            Console.WriteLine()
            RaiseEvent ConsoleOutputWrite(Self, New ConsoleOutputWriteEventArgs(ControlChars.NewLine))
        Else
            Console.WriteLine(text)
            RaiseEvent ConsoleOutputWrite(Self, New ConsoleOutputWriteEventArgs(text & ControlChars.NewLine))
        End If
    End Sub

    Protected Shared Sub WaitForMore(ByRef stopCounting As Boolean, ByRef lineCount As Integer)
        Console.Write("(more...)")
        If Console.ReadKey(True).Key = ConsoleKey.Escape Then
            stopCounting = True
        End If
        Console.CursorLeft = 0
        Console.Write("         ")
        Console.CursorLeft = 0
        lineCount = 1
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console, using String.Format.
    ''' </summary>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub Write(text As String, ParamArray textArgs() As Object)
        If text Is Nothing Then text = String.Empty
        Dim formatedString As String = String.Format(text, textArgs)
        DoWrite(formatedString)
    End Sub

    ''' <summary>
    ''' Writes the specified character to the console [count] times.
    ''' </summary>
    ''' <param name="character"></param>
    ''' <param name="count"></param>
    ''' <remarks></remarks>
    Public Shared Sub Write(character As Char, count As Integer)
        Dim outputString As New String(character, count)
        DoWrite(outputString)
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console, using String.Format and optionally adding a new-line character.
    ''' </summary>
    ''' <param name="lineBreak"></param>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub Write(lineBreak As Boolean, text As String, ParamArray textArgs() As Object)
        If lineBreak Then
            WriteLine(text, textArgs)
        Else
            Write(text, textArgs)
        End If
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console, using String.Format, followed by a new-line character.
    ''' </summary>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteLine(text As String, ParamArray textArgs() As Object)
        Dim formatedString As String = String.Format(text, textArgs)
        DoWriteLine(formatedString)
    End Sub

    ''' <summary>
    ''' Writes the specified number of empty lines to the console.
    ''' </summary>
    ''' <param name="count"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteLine(count As Integer)
        For i As Integer = 1 To count
            DoWriteLine()
        Next
    End Sub

    ''' <summary>
    ''' Writes an empty line to the console.
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared Sub WriteLine()
        WriteLine(1)
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console in the specified color, using String.Format and optionally adding a new-line character.
    ''' </summary>
    ''' <param name="textColor"></param>
    ''' <param name="lineBreak"></param>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteColored(textColor As ConsoleColor, lineBreak As Boolean, text As String, ParamArray textArgs() As Object)
        Dim formatedString As String = String.Format(text, textArgs)
        Dim currentColor As ConsoleColor = Console.ForegroundColor
        Console.ForegroundColor = textColor
        If lineBreak Then
            DoWriteLine(formatedString)
        Else
            DoWrite(formatedString)
        End If
        Console.ForegroundColor = currentColor
    End Sub

    ''' <summary>
    ''' Writes a string formatted with CommandConsoleFramework markup to the console using String.Format.
    ''' </summary>
    ''' <param name="formattedText"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks>
    ''' The [formattedText] paramter can be comprised of String.Format replacement codes ({0}) as well
    ''' as CommandConsoleFramework markup formatting codes (\[n]).  If textArgs() contains any elements,
    ''' then all {#} codes in [formattedText] will be replaced with the values of textArgs() according
    ''' to the functionality of String.Format.  Afterward, all \[@#] codes will be processed accordingly.
    ''' The possible markup formatting codes are:
    ''' <list>
    ''' <item>@=c, #=ConsoleColor, Sets the text color.</item>
    ''' <item>@=b, #=ConsoleColor, Sets the back color.</item>
    ''' <item>@=r, #=SpaceCount, Moves the cursor right by SpaceCount.</item>
    ''' <item>@=l, #=SpaceCount, Moves the cursor left by SpaceCount.</item>
    ''' <item>@=n, #=LineCount, Writes LineCount new-line characters. LineCount is optional and equals 1 if not provided.</item>
    ''' </list>
    ''' </remarks>
    Public Shared Sub WriteFormatted(formattedText As String, ParamArray textArgs() As Object)
        Dim formattedString As String
        If textArgs.Length = 0 Then
            formattedString = formattedText
        Else
            formattedString = String.Format(formattedText, textArgs)
        End If
        Dim parts() As String = Regex.Split(formattedString, "(\\\[[cblrn]\d*\])") '\w{1}
        Dim currentForeColor As ConsoleColor = Console.ForegroundColor
        Dim currentBackColor As ConsoleColor = Console.BackgroundColor
        Dim charCount As Integer = Console.CursorLeft
        Dim lineCount As Integer = 0
        Dim stopCounting As Boolean = False
        For Each part In parts
            Dim codeMatch As Match = Regex.Match(part, "(\\\[([cbrln])(\d*)\])")
            If codeMatch.Success Then
                Dim valueString As String = codeMatch.Groups(3).Value
                Dim value As Integer = 0
                If valueString.Length > 0 Then value = CInt(valueString)
                Dim commandChar As Char = codeMatch.Groups(2).Value
                If commandChar = "c"c Then
                    If value > 15 Then
                        Console.ForegroundColor = currentForeColor
                    Else
                        Console.ForegroundColor = value
                    End If
                ElseIf commandChar = "b"c Then
                    If value > 15 Then
                        Console.BackgroundColor = currentBackColor
                    Else
                        Console.BackgroundColor = value
                    End If
                ElseIf commandChar = "r"c Then
                    Console.CursorLeft += value
                    charCount += value
                ElseIf commandChar = "l"c Then
                    Console.CursorLeft -= value
                    charCount -= value
                ElseIf commandChar = "n"c Then
                    If value = 0 Then value = 1
                    charCount = 0
                    For i As Integer = 1 To value
                        DoWriteLine()
                        lineCount += 1
                        If Not stopCounting AndAlso lineCount >= Console.WindowHeight Then
                            WaitForMore(stopCounting, lineCount)
                        End If
                    Next
                End If
            Else
                Dim words As List(Of CommandLineWord) = CommandLine.GetWords(part)
                For index As Integer = 0 To words.Count - 1
                    Dim word As CommandLineWord = words(index)
                    If Not stopCounting Then
                        If word.Word.EndsWith(ControlChars.NewLine) Then
                            charCount = 0
                            lineCount += 1
                        ElseIf charCount > 0 AndAlso charCount + word.Word.Length > Console.WindowWidth - 1 Then
                            charCount = 0
                            lineCount += 1
                            DoWriteLine()
                        End If
                        If lineCount >= Console.WindowHeight Then
                            WaitForMore(stopCounting, lineCount)
                            charCount = 0
                        End If
                        charCount += word.Word.Length
                    End If
                    DoWrite(word.Word)
                Next
            End If
        Next
        Console.ForegroundColor = currentForeColor
        Console.BackgroundColor = currentBackColor
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console indented the specified number of spaces, using String.Format and optionally adding a new-line character.
    ''' </summary>
    ''' <param name="indent"></param>
    ''' <param name="lineBreak"></param>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteIndented(indent As Integer, lineBreak As Boolean, text As String, ParamArray textArgs() As Object)
        Console.CursorLeft += indent
        If lineBreak Then
            WriteLine(text, textArgs)
        Else
            Write(text, textArgs)
        End If
    End Sub

    ''' <summary>
    ''' Writes the specified text to the console indented the specified number of spaces, in the specified color, using String.Format and optionally adding a new-line character.
    ''' </summary>
    ''' <param name="indent"></param>
    ''' <param name="textColor"></param>
    ''' <param name="lineBreak"></param>
    ''' <param name="text"></param>
    ''' <param name="textArgs"></param>
    ''' <remarks></remarks>
    Public Shared Sub WriteIndentedColored(indent As Integer, textColor As ConsoleColor, lineBreak As Boolean, text As String, ParamArray textArgs() As Object)
        Console.CursorLeft += indent
        WriteColored(textColor, lineBreak, text, textArgs)
    End Sub

End Class

Public Class ConsoleOutputWriteEventArgs
    Inherits EventArgs

    Public ReadOnly Text As String
    Public Sub New(lineText As String)
        Text = lineText
    End Sub
End Class